﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using SSLLib;

namespace ExchangeHog.EHCore
{
	public class SSLTCPClientException : Exception
	{
		internal SSLTCPClientException(String message) : base(message) { }
	}

	public class SSLEnabledTCPClient
	{
		String server;
		Int32 port;
		TcpClient client;
		NetworkStream netstream;
		SSLTransport sslt;

		public SSLEnabledTCPClient(String server, Int32 port, bool useSSL)
		{
			this.server = server;
			this.port = port;
			if (useSSL)
				sslt = new SSLTransport();
		}

		public void connect()
		{
			client = new TcpClient(server, port);
			netstream = client.GetStream();
			netstream.ReadTimeout = 60 * 1000; // 1 minute timeout
			if (sslt != null)
			//{
			//	Console.Write("SSL handshake initated...");
				sslt.Handshake(
					server,
					(byte[] data) => sendRaw(data),
					(int maxlen) => readRaw(maxlen),
					out extradata);
			//	Console.WriteLine("done");
			//}
		}

		public void disconnect()
		{
			if (sslt != null)
				sslt.Done();
			netstream.Close();
			client.Close();
		}

		Byte[] extradata;
		private Byte[] readRaw(int maxlen)
		{
			Byte[] alldata = extradata;
			if (extradata != null)
//			{
//				Console.Write("Extra: " + alldata.Length);
				extradata = null;
//			}
			else
				do
				{
					Byte[] data = new Byte[1024];
					Int32 bytesread = 0;
//					Console.Write("R?");
					try
					{
						bytesread = netstream.Read(data, 0, data.Length);
					}
					catch (IOException e)
					{
						throw new SSLTCPClientException("Timed out while reading from " + server + ": port :" + e);
					}
//					Console.WriteLine(bytesread);
					var newdata = new Byte[(alldata != null? alldata.Length: 0) + bytesread];
					if (alldata != null) 
						Array.Copy(alldata, newdata, alldata.Length);
					Array.Copy(data, 0, newdata, (alldata != null? alldata.Length: 0), bytesread);
					alldata = newdata;
				} while (netstream.DataAvailable);

			if (alldata.Length > maxlen)
			{
				extradata = new Byte[alldata.Length - maxlen];
				Array.Copy(alldata, maxlen, extradata, 0, alldata.Length - maxlen);
				Array.Resize<Byte>(ref alldata, maxlen);
			}
			return alldata;
		}

		protected String readString()
		{
			var bytes = readRaw(SSLLib.SSLTransport.MAX_BUFFER_SIZE);

			String s = null;
			if (sslt != null && bytes.Length > 0)
			{
				var decryptedResponse = new StringBuilder();
				sslt.Decrypt(
					bytes,
					(byte[] data) => sendRaw(data),
					(int maxlen) => readRaw(maxlen),
					(byte[] data) => decryptedResponse.Append(ASCIIEncoding.ASCII.GetChars(data))
				);
				s = decryptedResponse.ToString();
			}
			else
				s = new String(ASCIIEncoding.ASCII.GetChars(bytes));
			return s;
		}

		protected void sendRaw(Byte[] data)
		{
			//Console.Write("W:");
			netstream.Write(data, 0, data.Length);
			//Console.Write(data.Length);
		}

		protected void sendBytes(byte[] data)
		{
			if (sslt != null)
				data = sslt.Encrypt(data);

			sendRaw(data);
		}

		protected void sendString(String s)
		{
			sendBytes(ASCIIEncoding.ASCII.GetBytes(s));
		}
	}
}
